package yagwl.client.model;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import yagwl.client.model.TreeNodeEvent.TreeNodeAction;
import yagwl.client.model.fnd.RpcStatus;

/**
 * Abstract class to manage cache of tree data on the client (browser)
 */
public abstract class TreeCache {

  private static Logger log = Logger.getLogger(TreeCache.class
      .getName());
  private TreeNodeDTO rootNode;
  private boolean loadInProgress = false;

  public TreeNodeDTO getRootNode() {
    return rootNode;
  }

  public abstract void fetch();

  protected boolean isLoadInProgress() {
    return loadInProgress;
  }

  protected void loadStart() {
    loadInProgress = true;
    log.info("loadStart: Loading " + this.getClass().getName());
  }

  protected void loadSuccess(TreeNodeDTO rootNode) {
    loadInProgress = false;
    this.rootNode = rootNode;
    rootNode.printTree();
    throwTreeEvent();
  }

  protected void loadFailure(Throwable caught) {
    loadInProgress = false;
    log.severe(caught.toString());
  }

  /**
   * Throws TreeEvent. Called when an asynchronous RPI for data
   * manipulation completes.
   */
  public void throwTreeEvent() {
    DataReadyEventBus.getInstance().fireEvent(new TreeEvent(this));
    log.info("throwTreeEvent: TreeEvent raised: " + this.getClass().getName());
  }

  public void createNode(TreeNodeDTO parentNode, String newNodeLabel) {
    final TreeNodeDTO parent = (TreeNodeDTO) parentNode;
    final TreeNodeDTO newNode = new TreeNodeDTO();
    // create a parent proxy to prevent passing all ancestors until root
    TreeNodeDTO parentProxy = new TreeNodeDTO();
    parentProxy.setId(parent.getId()); // only parent id is needed
    newNode.setParent(parentProxy);
    newNode.setLabel(newNodeLabel);
    newNode.put("name", newNode.getLabel());
    nodeCreate(parentNode, newNode);
  }

  protected void nodeCreated(TreeNodeDTO parentNode, TreeNodeDTO createdNode,
      RpcStatus result) {
    createdNode.setId(result.getObjectId());
    createdNode.setParent(parentNode);
    parentNode.addChild(createdNode);
    raiseTreeNodeEvent(TreeNodeAction.Create, createdNode);
  }

  public void deleteNode(TreeNodeDTO treeNode) {
    nodeDelete(treeNode);
  }

  protected void nodeDeleted(TreeNodeDTO treeNode, RpcStatus result) {
    TreeNodeDTO parentNode = treeNode.getParent();
    List<TreeNodeDTO> children = parentNode.getChildren();
    children.remove(treeNode);
    if (children.size() == 0)
      parentNode.setChildren(null);
    raiseTreeNodeEvent(TreeNodeAction.Delete, treeNode);
  }

  public void updateNode(TreeNodeDTO origNode, TreeNodeDTO updatedNodeProxy) {
    updatedNodeProxy.setId(origNode.getId());
    if (origNode.getParent() != null) {
      TreeNodeDTO parentNodeProxy = new TreeNodeDTO();
      parentNodeProxy.setId(origNode.getParent().getId());
      updatedNodeProxy.setParent(parentNodeProxy);
    }
    nodeUpdate(origNode, updatedNodeProxy);
  }

  protected void nodeUpdated(TreeNodeDTO origNode,
      TreeNodeDTO updatedNodeProxy, RpcStatus result) {
    if (result.getErrorCode() == null) {
      origNode.getMap().putAll(updatedNodeProxy.getMap());
    }
    raiseTreeNodeEvent(TreeNodeAction.Update, origNode);
  }

  public void updateNodeLabel(TreeNodeDTO origNode, String label) {
    TreeNodeDTO updatedNodeProxy = new TreeNodeDTO();
    updatedNodeProxy.setId(origNode.getId());
    updatedNodeProxy.getMap().putAll(origNode.getMap());
    updatedNodeProxy.setLabel(label);
    if (origNode.getParent() != null) {
      TreeNodeDTO parentNodeProxy = new TreeNodeDTO();
      parentNodeProxy.setId(origNode.getParent().getId());
      updatedNodeProxy.setParent(parentNodeProxy);
    }
    nodeLabelUpdate(origNode, updatedNodeProxy);
  }

  protected void nodeLabelUpdated(TreeNodeDTO origNode,
      TreeNodeDTO updatedNodeProxy, RpcStatus result) {
    origNode.setLabel((String) updatedNodeProxy.getLabel());
    raiseTreeNodeEvent(TreeNodeAction.UpdateLabel, origNode);
  }

  public void addItem(TreeNodeDTO parentNode, ItemDTO item) {
    itemAdd(parentNode, item);
  }

  protected void itemAdded(TreeNodeDTO parentNode, ItemDTO item,
      RpcStatus result) {
    if (parentNode.getItems() == null)
      parentNode.setItems(new ArrayList<ItemDTO>());
    parentNode.getItems().add(item);
    raiseTreeNodeEvent(TreeNodeAction.AddItem, parentNode);
  }

  public void removeItem(TreeNodeDTO parentNode, ItemDTO item) {
    itemRemove(parentNode, item);
  }

  protected void itemRemoved(TreeNodeDTO parentNode, ItemDTO item,
      RpcStatus result) {
    parentNode.getItems().remove(item);
    if (parentNode.getItems().size() == 0)
      parentNode.setItems(null);
    raiseTreeNodeEvent(TreeNodeAction.RemoveItem, parentNode);
  }

  private void raiseTreeNodeEvent(TreeNodeAction action, TreeNodeDTO treeNode) {
    DataReadyEventBus.getInstance().fireEvent(
        new TreeNodeEvent(this.getClass().getName(), action, treeNode));
    log.info("raiseTreeNodeEvent: TreeNodeEvent raised: " + action + " "
        + this.getClass().getName());
  }

  protected abstract void nodeCreate(TreeNodeDTO parentNode, TreeNodeDTO newNode);

  protected abstract void nodeDelete(TreeNodeDTO treeNode);

  protected abstract void nodeUpdate(TreeNodeDTO origNode,
      TreeNodeDTO updatedNode);

  protected abstract void nodeLabelUpdate(TreeNodeDTO origNode,
      TreeNodeDTO updatedNode);

  protected abstract void itemAdd(TreeNodeDTO parentNode, ItemDTO item);

  protected abstract void itemRemove(TreeNodeDTO parentNode, ItemDTO item);
}
